package org.example.bean;


import lombok.Data;
import org.example.util.MapUtil;

import java.util.*;

/**
 * @Desc:游戏地图信息
 * @Author: yanghongjun
 * @Date: 2024/5/6
 **/
@Data
public class GameMap {
    //地图所有的1*1的格子
    private int[][] maps;
    //偏移量，用来转换坐标
    private int offsetX;
    private int offsetY;
    //地图格子长宽
    private int weight;
    private int height;
    //8个方向
    private final int[][] moves = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {-1, -1}, {-1, 1}, {1, -1}, {1, 1}};





    /**
     * @param minX
     * @param maxX
     * @param minY
     * @param maxY
     */
    public GameMap(int minX, int maxX, int minY, int maxY) {
        weight = Math.abs(minX) + maxX;
        height = Math.abs(minY) + maxY;
        //找到x和y方向上的最小值和偏移量
        offsetX = Math.abs(minX);
        offsetY = Math.abs(minY);
        //确定二维数组的大小，要包括偏移量
        int width = Math.abs(maxX - minX) + 1 + 2 * offsetX;
        int height = Math.abs(maxY - minY) + 1 + 2 * offsetY;
        //初始化二维数组
        maps = new int[height][width];
    }


    /**
     * 将游戏坐标转换为LazyNode。
     *
     * @param gameX 游戏内的X坐标
     * @param gameY 游戏内的Y坐标
     * @return 一个包含地图数组索引的整数数组，第一个元素是X索引，第二个元素是Y索引
     */
    public LazyNode gameToMapIndex(float gameX, float gameY) {
        float mapX = gameX + offsetX;
        float mapY = gameY + offsetY;
        return new LazyNode((int) (mapX), (int) (mapY));
    }

    /**
     * 将地图LazyNode转换回游戏内的坐标。
     *
     * @param mapX 地图数组的X索引
     * @param mapY 地图数组的Y索引
     * @return 一个包含游戏内坐标的整数数组，第一个元素是X坐标，第二个元素是Y坐标
     */
    public JsonGamePos mapToGameCoords(int mapX, int mapY) {
        int gameX = mapX - offsetX;
        int gameY = mapY - offsetY;
        return new JsonGamePos((float) gameX, (float) gameY);
    }

    /**
     * 在地图上添加一个圆形阻挡区域。
     *
     * @param centerX 圆形阻挡的中心点X坐标（游戏坐标）
     * @param centerY 圆形阻挡的中心点Y坐标（游戏坐标）
     * @param radius  圆形阻挡的半径
     */
    public void addCircularObstacle(float centerX, float centerY, int radius) {
        // 转换为地图坐标
        int mapCenterX = (int) (centerX + offsetX);
        int mapCenterY = (int) (centerY + offsetY);
        // 遍历圆形区域内的每个点，检查并设置阻挡
        for (int x = mapCenterX - radius; x <= (mapCenterX + radius); x++) {
            for (int y = (int) (mapCenterY - radius); y <= (mapCenterY + radius); y++) {
                // 判断点 (x, y) 是否在圆形区域内，并在区域内设置阻挡
                if (isInsideCircle(mapCenterX, mapCenterY, x, y, radius)) {
                    // 确保索引在地图范围内
                    if (x >= 0 && x < maps[0].length && y >= 0 && y < maps.length) {
                        // 设置为阻挡
                        maps[x][y] = 1;
                    }
                }
            }
        }
    }

    /**
     * 判断一个点是否在以 (cx, cy) 为中心，r 为半径的圆内。
     *
     * @param cx 圆的中心点X坐标
     * @param cy 圆的中心点Y坐标
     * @param x  要检查的点的X坐标
     * @param y  要检查的点的Y坐标
     * @param r  圆的半径
     * @return 如果点在圆内，则返回 true；否则返回 false
     */
    public boolean isInsideCircle(float cx, float cy, float x, float y, int r) {
        float distance = (float) Math.sqrt(Math.pow(cx - x, 2) + Math.pow(cy - y, 2));
        return distance <= r;
    }

    /**
     * 从地图上移除一个圆形阻挡区域。
     *
     * @param centerX 圆形阻挡的中心点X坐标（游戏坐标）
     * @param centerY 圆形阻挡的中心点Y坐标（游戏坐标）
     * @param radius  圆形阻挡的半径
     */
    public void removeCircularObstacle(float centerX, float centerY, int radius) {
        // 转换为地图坐标
        float mapCenterX = centerX + offsetX;
        float mapCenterY = centerY + offsetY;
        // 遍历圆形区域内的每个点，清除阻挡
        for (int x = (int) (mapCenterX - radius); x <= (mapCenterX + radius); x++) {
            for (int y = (int) (mapCenterY - radius); y <= (mapCenterY + radius); y++) {
                // 判断点 (x, y) 是否在圆形区域内，并在区域内清除阻挡
                if (isInsideCircle(mapCenterX, mapCenterY, x, y, radius)) {
                    // 确保索引在地图范围内
                    if (x >= 0 && x < maps[0].length && y >= 0 && y < maps.length) {
                        // 清除阻挡
                        maps[x][y] = 0;
                    }
                }
            }
        }
    }

    /**
     * 检查两个节点是否可以直线相连而不穿过障碍物。
     *
     * @param x1 起点x
     * @param y1 起点y
     * @param x2 终点x
     * @param y2 终点y
     * @return 如果两点之间可以直线相连并且没有障碍物则返回true，否则返回false
     */
    public boolean canConnectStraight(int x1, int y1, int x2, int y2) {
        int dx = Math.abs(x2 - x1);
        int dy = Math.abs(y2 - y1);
        int sx = (x1 < x2) ? 1 : -1;
        int sy = (y1 < y2) ? 1 : -1;
        int err = dx - dy;
        while (true) {
            if (x1 >= 0 && x1 < weight && y1 >= 0 && y1 < height && maps[x1][y1] != 0) {
                // 路径上有障碍物
                return false;
            }
            if (x1 == x2 && y1 == y2) {
                break; // 到达终点
            }
            int e2 = err * 2;
            if (e2 > -dy) {
                err -= dy;
                x1 += sx;
            }
            if (e2 < dx) {
                err += dx;
                y1 += sy;
            }
        }
        // 路径上无障碍物
        return true;
    }

    /**
     * 判断给定坐标是否在网格地图范围内且不是障碍物。
     *
     * @param x 横坐标
     * @param y 纵坐标
     * @return 若坐标在网格范围内且不是障碍物则返回 true，否则返回 false
     */
    public boolean isValid(int x, int y) {
        return x >= 0 && x < weight && y >= 0 && y < height && maps[x][y] != 1;
    }

    /**
     * 检查给定的坐标是否在网格的边界内。
     *
     * @param x 坐标x
     * @param y 坐标y
     * @return 如果坐标在网格内则返回true，否则返回false
     */
    public boolean isInBounds(int x, int y) {
        return x >= 0 && x < weight && y >= 0 && y < height;
    }

    /**
     * 优化路径，通过去除不必要的节点来简化路径。
     *
     * @param path 原始路径
     * @return 优化后的路径
     */
    public List<JsonGamePos> optimizePath(List<Cord> path) {
        if (path == null || path.isEmpty()) {
            return null;
        }
        if (path.size() >= 3) {
            List<Cord> optimizedPath = new ArrayList<>();
            // 起点总是加入优化路径
            optimizedPath.add(path.get(0));
            Cord lastOptimizedNode = path.get(0);
            for (int i = 1; i < path.size() - 1; i++) {
                Cord currentNode = path.get(i);
                Cord nextNode = path.get(i + 1);
                // 检查当前节点是否可以与下一个节点直线相连而不穿过障碍物
                if (canConnectStraight(lastOptimizedNode.getX(), lastOptimizedNode.getY(), nextNode.getX(), nextNode.getY())) {
                    // 如果可以直线相连，则跳过当前节点
                    continue;
                }
                // 否则，将当前节点添加到优化路径中
                optimizedPath.add(currentNode);
                lastOptimizedNode = currentNode;
            }
            // 终点总是加入优化路径
            optimizedPath.add(path.get(path.size() - 1));
            path = optimizedPath;
        }
        List<JsonGamePos> posList = new ArrayList<>();
        for (Cord lazyNode : path) {
            JsonGamePos jsonGamePos = mapToGameCoords(lazyNode.getX(), lazyNode.getY());
            posList.add(jsonGamePos);
        }
        return posList;
    }


    /**
     * 获取给定节点的所有邻居节点。
     *
     * @param node 给定节点
     * @return 邻居节点列表
     */
    public List<LazyNode> getNeighbors(LazyNode node) {
        List<LazyNode> neighbors = new ArrayList<>();
        for (int i = 0; i < moves.length; i++) {
            int[] move = moves[i];
            int nextX = node.getX() + move[0];
            int nextY = node.getY() + move[1];
            if (nextX >= 0 && nextY >= 0 && nextX < weight && nextY < height && maps[nextX][nextY] == 0) {
                neighbors.add(new LazyNode(nextX, nextY, i + 1));
            }
        }
        return neighbors;
    }
}
